#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <getopt.h> /* getopt_long() */

#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <iostream>
#include <fstream>
using namespace std;
const int maxCount = 4;
typedef struct _BUFTYPE
{
    unsigned char *start;
    int length;
} BUFTYPE;

#define IOCTL(x, y, z)                    \
    if (ioctl(x, y, z) < 0)               \
    {                                     \
        cout << __LINE__ << " failed.\n"; \
        return -1;                        \
    }

int main()
{
    // open
    int fd = open("/dev/video0", O_RDWR, 0);
    if (fd < 0)
    {
        cout << "open failed\n";
    }

    // init dev
    struct v4l2_capability cap;
    int ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    cout << "cap:" << cap.capabilities << endl;
    cout << "driver:" << cap.driver << endl;
    cout << "card:" << cap.card << endl;
    if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
    {
        cout << "V4L2_CAP_VIDEO_CAPTURE:" << endl;
    }
    if (cap.capabilities & V4L2_CAP_READWRITE)
    {
        cout << "V4L2_CAP_READWRITE:" << endl;
    }
    if (cap.capabilities & V4L2_CAP_STREAMING)
    {
        cout << "V4L2_CAP_STREAMING:" << endl;
    }

    // get fmt
    struct v4l2_fmtdesc fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0)
    {
        cout << "pixel:" << fmt.pixelformat << endl;
        // get frame size
        struct v4l2_frmsizeenum fsize;
        memset(&fsize, 0, sizeof(fsize));
        fsize.index = 0;
        fsize.pixel_format = fmt.pixelformat;
        while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsize) >= 0)
        {

            if (fsize.type == V4L2_FRMSIZE_TYPE_DISCRETE)
            {
                printf("{ discrete: width = %u, height = %u }\n",
                       fsize.discrete.width, fsize.discrete.height);
                // 查询每种分辨率支持帧率
                struct v4l2_frmivalenum framival;
                framival.index = 0;
                framival.pixel_format = fmt.pixelformat;
                framival.width = fsize.discrete.width;
                framival.height = fsize.discrete.height;
                while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &framival) >= 0)
                {
                    framival.index++;
                    printf("frame interval: %d, %d \n", framival.discrete.numerator, framival.discrete.denominator);
                }
            }
            else if (fsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)
            {
                printf("{ continuous: min { width = %u, height = %u } .. "
                       "max { width = %u, height = %u } }\n",
                       fsize.stepwise.min_width, fsize.stepwise.min_height,
                       fsize.stepwise.max_width, fsize.stepwise.max_height);
                printf("  Refusing to enumerate frame intervals.\n");
                break;
            }
            else if (fsize.type == V4L2_FRMSIZE_TYPE_STEPWISE)
            {
                printf("{ stepwise: min { width = %u, height = %u } .. "
                       "max { width = %u, height = %u } / "
                       "stepsize { width = %u, height = %u } }\n",
                       fsize.stepwise.min_width, fsize.stepwise.min_height,
                       fsize.stepwise.max_width, fsize.stepwise.max_height,
                       fsize.stepwise.step_width, fsize.stepwise.step_height);
                printf("  Refusing to enumerate frame intervals.\n");
                break;
            }
            fsize.index++;
        }
        cout << "support type:" << fmt.description << endl;
        fmt.index++;
    }

    // set fmt
    struct v4l2_format tv_fmt;
    tv_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    //  上 面  查出 能  支持640*480，   直接    设置
    tv_fmt.fmt.pix.width = 640;
    tv_fmt.fmt.pix.height = 480;
    tv_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    tv_fmt.fmt.pix.field = V4L2_FIELD_ANY; //V4L2_FIELD_INTERLACED;//
    IOCTL(fd, VIDIOC_S_FMT, &tv_fmt);
    // init mmap
    struct v4l2_requestbuffers reqbuf;
    memset(&reqbuf, 0, sizeof(reqbuf));
    reqbuf.count = maxCount;
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    ioctl(fd, VIDIOC_REQBUFS, &reqbuf);

    BUFTYPE *usrBuf = (BUFTYPE *)malloc(sizeof(BUFTYPE) * maxCount);
    memset(usrBuf, 0, sizeof(BUFTYPE) * maxCount);

    // map kernel cache to user process
    struct v4l2_buffer buf;
    for (int i = 0; i < reqbuf.count; ++i)
    {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        IOCTL(fd, VIDIOC_QUERYBUF, &buf);
        usrBuf[i].length = buf.length;
        usrBuf[i].start = (unsigned char *)mmap(nullptr, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        IOCTL(fd, VIDIOC_QBUF, &buf);
    }

    // start capture
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    IOCTL(fd, VIDIOC_STREAMON, &type);
    fstream fs;
    fs.open("tmp.yuv", ios::app | ios::binary);
    fd_set fds;
    struct timeval tv;
    int nCount = -1;

    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    int iRet;

    tv.tv_sec = 0;
    tv.tv_usec = 333;

    for (int i = 0; i < 10; ++i)
    {
        struct v4l2_buffer tmp;
        memset(&tmp, 0, sizeof(tmp));
        tmp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        tmp.memory = V4L2_MEMORY_MMAP;
        IOCTL(fd, VIDIOC_DQBUF, &tmp);

        fs.write((const char *)usrBuf[tmp.index].start, tmp.bytesused);
        cout << "bytesused:" << tmp.bytesused << " length:" << tmp.length << endl;

        IOCTL(fd, VIDIOC_QBUF, &tmp);
        select(fd + 1, &fds, NULL, NULL, &tv);
    }
    cout << "end\n";
    fs.close();
    close(fd);
    return 0;
}
